# load.py: Loads in Major, Course, and Section data (from their respective
#          json files) and dumps the data into the database

from django.core.management.base import BaseCommand, CommandError
from alpha.models import Building
from alpha.models import Room
from alpha.models import TimeBlock
from alpha.models import SectionTimeBlock
from alpha.models import Major
from alpha.models import Course
from alpha.models import Section
import class_parser
import urllib
import re
import datetime
import Queue
import threading
import time
import sys
import json

time_re = re.compile('([0-9]{1,4})-([0-9]{1,4})([PA])')
catalog_re = re.compile('<TD><FONT SIZE="-1"><B>Description: </B>(.*?)</FONT></TD>')
GENERAL_CATALOG_URL = 'http://osoc.berkeley.edu/catalog/gcc_search_sends_request'

DAYS = {
  'M':  0,
  'Tu': 1,
  'W':  2,
  'Th': 3,
  'F':  4,
  'SA': 5,
  'Su': 6,
}

class Counter(object):
  def __init__(self):
    self.lock = threading.Lock()
    self.count = 0
    
  def inc(self):
    with self.lock:
      self.count += 1
     
  def cur(self):
    with self.lock:
      return self.count


class Command(BaseCommand):
  def handle(self, *args, **options):

    majors = json.load(open('majors.json'))
    # Now that we have the majors, lets store them in the database
    print 'Saving majors in database (%d entries)...' % len(majors),
    new_entries = 0
    for major_shortname, major_longname in majors:
      # print '%s (%s)' % (major_shortname, major_longname)
      major, created = Major.objects.get_or_create(shortname=major_shortname)
      if created: 
        new_entries += 1
        major.longname = major_longname
        major.save()
    print 'done [%d entries created]' % new_entries
    
    # In the meantime, we can also store all the classes information in the db
    courses = json.load(open('classes.json'))
    print 'Saving courses in database (%d entries)...' % len(courses)
    new_entries = 0
    count = 0
    for major_shortname, code, title, url, description in courses:
      count += 1
      print '(%4d)%s %s - %s' % (count, major_shortname, code, title),
      major = Major.objects.get(shortname=major_shortname)
      course, created = Course.objects.get_or_create(code=code, major=major,
          defaults={'title': title, 'description': description})
      if created:
        print '\tCreated',
        new_entries += 1
        course.save()
      print
    print 'done [%d entries created]' % new_entries
    
    sections = json.load(open('sections.json'))
    count = 0
    total = len(sections)
    for section_data in sections:
      count += 1
      (major_shortname, code, section_type, section_number, parent, 
          section_time, section_location, instructor, ccn,  last_changed,
          book_url, primary) = section_data
      print '%4d/%4d\t%s %s - %s %s (%s)' % (count, total, major_shortname, 
          code, section_number, section_type, section_location)
      # print section_data
      if count % 1000 == 0: time.sleep(2)  # Sleep for a bit every 1000 to get by the cpu quota
      try:
        major = Major.objects.get(shortname=major_shortname)
        course = Course.objects.get(major=major, code=code)
      except Exception:  # Does not exist
        continue
      try:
        ccn = int(ccn)
      except Exception:  # TODO: Use a more specific exception?
        ccn = None
      if Section.objects.filter(course=course, 
          number=section_number, type=section_type):
        continue  # Skip this one if we've already done it.
      if section_location:
        cleaned_location_groups = re.search('([0-9A-Za-z ]+)', section_location)
        cleaned_location = cleaned_location_groups.group(1)
        location_groups = re.search('([A-Z]*[0-9]+[A-Z]*) (.+)', cleaned_location)
        if location_groups:
          room_name, building_name = location_groups.groups()
          room_name = room_name.strip()
          building_name = building_name.strip()
        elif cleaned_location == 'WHEELER AUD':  # Special Case
          building_name = 'WHEELER'
          room_name = 'AUD'
        else:
          building_name = cleaned_location.strip()
          if building_name == 'NOFACILITY':  # Another special case
            building_name = 'NO FACILITY'
          room_name = 'N/A'
        building, created = Building.objects.get_or_create(shortname=building_name)
        if created: building.save()
        room, created = Room.objects.get_or_create(building=building, name=room_name)
        if created: room.save()
      else:
        building, created = Building.objects.get_or_create(shortname='N/A')
        if created: building.save()
        room_name = 'N/A'
        room, created = Room.objects.get_or_create(building=building, name=room_name)
        if created: room.save()
        
      # Prepare time blocks
      time_blocks = []
      section_time_blocks = []
      if section_time and len(section_time.split(' ')) > 1:
        # Is in the format of Tu 7-8P, or MWF 930-11A
        days, times = section_time.split(' ')
        days_groups = re.findall('(M|Tu|W|Th|F|SA|Su)', days)
        if not days_groups:
          continue
        time_groups = time_re.search(times)
        start_time_str, end_time_str, am_pm = time_groups.groups()
        
        # Create python datetime objects.  We use datetime because time doesn't
        # support add/sub operations with timedelta objects
        if len(start_time_str) > 2:
          if len(start_time_str) == 3:
            start_time_hour = int(start_time_str[0])
          else:
            start_time_hour = int(start_time_str[:2])
          start_time_minutes = int(start_time_str[-2:])
          start_time = datetime.datetime(2000, 1, 1, start_time_hour, start_time_minutes)
        else:
          start_time_hour = int(start_time_str)
          start_time_minutes = 0
          start_time = datetime.datetime(2000, 1, 1, start_time_hour)

        if len(end_time_str) > 2:
          if len(end_time_str) == 3:
            end_time_hour = int(end_time_str[0])
          else:
            end_time_hour = int(end_time_str[:2])
          end_time_minutes = int(end_time_str[-2:])
          end_time = datetime.datetime(2000, 1, 1, end_time_hour, end_time_minutes)
        else:
          end_time_hour = int(end_time_str)
          end_time_minutes = 0
          end_time = datetime.datetime(2000, 1, 1, end_time_hour)

        # Add 12 hours if its PM, unless its 12PM
        if am_pm == 'P' and end_time_hour != 12:
          half_day_delta = datetime.timedelta(hours=12)
          end_time = end_time + half_day_delta
          # Only add the half day delta if start_time < end_time
          if start_time_hour < end_time_hour or start_time_hour == 12:
            start_time = start_time + half_day_delta
        
        # Create time blocks and section time blocks
        half_hour = datetime.timedelta(minutes=30)
        for day in days_groups:
          current_start_time = start_time
          day_code = DAYS[day]  # Convert MTuWThF to database format (0, 1, ...)
          # We need to keep track of consecutive time blocks
          todays_time_blocks = []
          # We're going to destructively loop over start time
          while current_start_time < end_time:
            block_end_time = current_start_time + half_hour
            time_block, created = TimeBlock.objects.get_or_create(day=day_code, 
                start_time=current_start_time.time(), end_time=block_end_time.time())
            if created:
              time_block.description = '%s %s-%s' % (time_block.get_day_display(),
                  current_start_time.strftime('%I:%M%p'), block_end_time.strftime('%I:%M%p'))
              # print 'New time block ', time_block.description 
              time_block.save()
            # Add time block into section, then increment
            time_blocks.append(time_block)
            todays_time_blocks.append(time_block)
            current_start_time = current_start_time + half_hour
          # Create a new section time block for all the consecutive time blocks we have
          section_time_block, created = SectionTimeBlock.objects.get_or_create(
              start_time=start_time, end_time=end_time, day=day_code, room=room)
          if created:
            section_time_block.description = '%s %s' % (day, times)
          for time_block in todays_time_blocks:
            section_time_block.time_blocks.add(time_block)
          section_time_block.save()
          section_time_blocks.append(section_time_block)
      
      # Create section
      section, created = Section.objects.get_or_create(course=course, 
          number=section_number, type=section_type, defaults={
          'ccn': ccn,
          'building': building,
          'room': room,
          'time': section_time or 'N/A',
          'instructor': instructor
          })
      for time_block in time_blocks:
        section.time_blocks.add(time_block)
      for section_time_block in section_time_blocks:
        section.section_time_blocks.add(section_time_block)
      if created:
        section.save()
      